package com.runvp.common.util.enc.aes128gcm;

import com.runvp.common.config.Aes128GcmConfig;
import com.runvp.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
@Slf4j
public class Aes128GcmUtils {

    static final String ALGO = "AES";
    static final String GCMALGO = "AES/GCM/NoPadding";
    static final String UNICODE_FORMAT = "UTF8";


    private String myKey;
    /**
     * myIv
     */
    private String myIv;

    public Aes128GcmUtils setMyKey(String myKey) {
        this.myKey = myKey;
        return this;
    }

    public Aes128GcmUtils setMyIv(String myIv) {
        this.myIv = myIv;
        return this;
    }

    // 静态内部类 类初始化时，立即加载这个对象（没有延时加载的优势）。加载类时，天然的是线程安全的。
    public static class SingletonClassInstance {
        private static final Aes128GcmUtils instance = new Aes128GcmUtils();
    }

    // 私有化构造器
    private Aes128GcmUtils() {
    }

    // 方法没有同步，调用效率高
    public static Aes128GcmUtils getInstance() {
        return SingletonClassInstance.instance;
    }




    /**
     * aes-128-gcm 固定iv key 加密
     * @param data
     * @return str
     */
    public String encrypt(String data) {
        try {
            String mainKey = this.myKey;
            String myIv = this.myIv;
            if(StringUtils.isBlank(mainKey)){
                mainKey = Aes128GcmConfig.getMyKey();
            }
            if(StringUtils.isBlank(myIv)){
                myIv = Aes128GcmConfig.getMyIv();
            }
            final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
            byte[] initializationVector = myIv.getBytes(UNICODE_FORMAT);
            SecretKeySpec secretKeySpec = new SecretKeySpec(mainKey.getBytes(UNICODE_FORMAT), ALGO);
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
            Cipher cipher = Cipher.getInstance(GCMALGO);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
            final byte[] encryptedData = cipher.doFinal(dataBytes);
            final byte[] encryptedBytes = encryptedData;
            System.out.println("data [String]        : " + data);
            System.out.println("data           length: " + dataBytes.length
                    + " data: " + bytesToHex(dataBytes));
            System.out.println("mainKey        length: " + mainKey.getBytes(UNICODE_FORMAT).length
                    + " data: " + bytesToHex(mainKey.getBytes(UNICODE_FORMAT)));
            System.out.println("initvector     length: " + initializationVector.length
                    + " data: " + bytesToHex(initializationVector));
            System.out.println("encryptedBytes length: " + encryptedBytes.length
                    + " data: " + bytesToHex(encryptedBytes));
            return bytesToHex(encryptedBytes).toUpperCase();
        }catch (Exception e){
            log.error("aes-128-gcm加密失败:{}",e.getMessage());
            return "";
        }
    }

    /**
     * aes-128-gcm 固定iv key tag 解密
     * @param data
     * @return str
     */
    public String decrypt(String data) {
        try {
            String mainKey = this.myKey;
            String myIv = this.myIv;
            final byte[] encryptedBytes = HexToBinaryArray.hex2BinArr(data);
            final byte[] initializationVector = myIv.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec secretKeySpec = new SecretKeySpec(mainKey.getBytes(UNICODE_FORMAT), ALGO);
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
            Cipher cipher = Cipher.getInstance(GCMALGO);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
            return new String(cipher.doFinal(encryptedBytes), UNICODE_FORMAT);
        }catch (Exception e){
            log.error("aes-128-gcm解密失败:{}",e.getMessage());
            return "";
        }

    }

    private static String bytesToHex(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
        return result.toString();
    }

}